package com.ttg.utils.sm2;


import com.ttg.utils.sm.CryptoUtil;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.Security;
import java.util.Enumeration;

public class SMUtil {

    static {
        try {
            Security.addProvider(new BouncyCastleProvider());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static X9ECParameters x9ECParameters = GMNamedCurves
            .getByName("sm2p256v1");
    private static ECDomainParameters ecDomainParameters = new ECDomainParameters(
            x9ECParameters.getCurve(), x9ECParameters.getG(),
            x9ECParameters.getN());
    private static ECParameterSpec ecParameterSpec = new ECParameterSpec(
            x9ECParameters.getCurve(), x9ECParameters.getG(),
            x9ECParameters.getN());

    public static KeyPair generateKeyPair() {
        try {
            KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", "BC");
            kpGen.initialize(ecParameterSpec, SecureRandomUtil.getSecureRandomInstance());
            KeyPair kp = kpGen.generateKeyPair();
            return kp;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] privateKey(byte[] privatekey) throws Exception {
        ASN1Sequence seq = ASN1Sequence.getInstance(privatekey);

        ASN1Encodable asn1Encodable = seq.getObjectAt(2);
        DEROctetString eEROctetString = (DEROctetString) asn1Encodable;

        DLSequence dLSequence = (DLSequence) ASN1Sequence
                .fromByteArray(eEROctetString.getOctets());
        asn1Encodable = dLSequence.getObjectAt(1);
        eEROctetString = (DEROctetString) asn1Encodable;
        return eEROctetString.getOctets();
    }

    public static byte[] publicKey(byte[] pubkey) throws Exception {
        SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo
                .getInstance(pubkey);

//        DERBitString publicKeyData = subjectPublicKeyInfo.getPublicKeyData();
        ASN1BitString publicKeyData = subjectPublicKeyInfo.getPublicKeyData();
        byte[] publicKey = publicKeyData.getEncoded();
        byte[] encodedPublicKey = publicKey;
        byte[] ecP = new byte[64];
        System.arraycopy(encodedPublicKey, 4, ecP, 0, ecP.length);

        byte[] certPKX = new byte[32];
        byte[] certPKY = new byte[32];
        System.arraycopy(ecP, 0, certPKX, 0, 32);
        System.arraycopy(ecP, 32, certPKY, 0, 32);
        return ecP;
    }

    public static byte[] decodeDERSignature(byte[] signature) throws Exception {
        ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(signature));

        try {
            ASN1Sequence primitive = (ASN1Sequence) stream.readObject();
            Enumeration enumeration = primitive.getObjects();
            BigInteger R = ((ASN1Integer) enumeration.nextElement()).getValue();
            BigInteger S = ((ASN1Integer) enumeration.nextElement()).getValue();
            byte[] bytes = new byte[64];
            byte[] r = format(R.toByteArray());
            byte[] s = format(S.toByteArray());
            System.arraycopy(r, 0, bytes, 0, 32);
            System.arraycopy(s, 0, bytes, 32, 32);
            return bytes;
        } catch (Exception var10) {
            throw new Exception();
        }
    }

    public static byte[] format(byte[] value) {
        if (value.length == 32) {
            return value;
        } else {
            byte[] bytes = new byte[32];
            if (value.length > 32) {
                System.arraycopy(value, value.length - 32, bytes, 0, 32);
            } else {
                System.arraycopy(value, 0, bytes, 32 - value.length, value.length);
            }

            return bytes;
        }
    }

    public static byte[] sm2EncryptDataForPubKey(byte[] data, String pubKey) {
        return sm2EncryptDataForPubKey(data, pubKey, true);
    }

    /**
     * SM2公钥加密
     * @param data
     * @param pubKey
     * @param isC1C2C3 默认返回C1C2C3模式
     * @return
     */
    public static byte[] sm2EncryptDataForPubKey(byte[] data, String pubKey, boolean isC1C2C3) {
        if (pubKey.length() != 128) {
            return null;
        }
        String x = pubKey.substring(0, 64);
        String y = pubKey.substring(64, 128);
        PublicKey key = getPublickeyFromXY(
                new BigInteger(
                        x,
                        16),
                new BigInteger(
                        y,
                        16));

        BCECPublicKey localECPublicKey = (BCECPublicKey) key;
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(
                localECPublicKey.getQ(), ecDomainParameters);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters,
                SecureRandomUtil.getSecureRandomInstance()));
        try {
            byte[] result = sm2Engine.processBlock(data, 0, data.length);
            if (!isC1C2C3) {
                result = SMUtil.changeC1C2C3ToC1C3C2(result);
            }
            return result;
        } catch (InvalidCipherTextException e) {
            throw new RuntimeException(e);
        }
    }

    public static BCECPublicKey getPublickeyFromXY(BigInteger x, BigInteger y) {
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters
                .getCurve().createPoint(x, y), ecParameterSpec);
        return new BCECPublicKey("EC", ecPublicKeySpec,
                BouncyCastleProvider.CONFIGURATION);
    }

    public static byte[] sm2DecryptDataForPriKey(byte[] data, String priKey) {
        return sm2DecryptDataForPriKey(data, priKey, true);
    }

    /**
     * SM2私钥解密
     * @param data
     * @param priKey
     * @param isC1C2C3 默认是C1C2C3模式；如需要传入C1C3C2模式，需要传=0
     * @return
     */
    public static byte[] sm2DecryptDataForPriKey(byte[] data, String priKey, boolean isC1C2C3) {
        if (!isC1C2C3) {
            data = SMUtil.changeC1C3C2ToC1C2C3(data);
        }
        BigInteger d = new BigInteger(
                priKey,
                16);
        BCECPrivateKey localECPrivateKey = getPrivatekeyFromD(d);
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(
                localECPrivateKey.getD(), ecDomainParameters);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(false, ecPrivateKeyParameters);
        try {
            return sm2Engine.processBlock(data, 0, data.length);
        } catch (InvalidCipherTextException e) {
            throw new RuntimeException(e);
        }
    }

    public static BCECPrivateKey getPrivatekeyFromD(BigInteger d) {
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d,
                ecParameterSpec);
        return new BCECPrivateKey("EC", ecPrivateKeySpec,
                BouncyCastleProvider.CONFIGURATION);
    }

    /**
     * 将SM2的签名从DER格式转成ASN1
     *
     * @param signature
     * @return
     * @throws Exception
     */
    public static byte[] encodeDERSignature(byte[] signature) throws Exception {
        byte[] r = new byte[32];
        byte[] s = new byte[32];
        System.arraycopy(signature, 0, r, 0, 32);
        System.arraycopy(signature, 32, s, 0, 32);
        ASN1EncodableVector vector = new ASN1EncodableVector();
        vector.add(new ASN1Integer(new BigInteger(1, r)));
        vector.add(new ASN1Integer(new BigInteger(1, s)));
        return (new DERSequence(vector)).getEncoded();
    }

    public static byte[] decodeDERSignature2(byte[] signature) throws Exception {
        ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(signature));
        ASN1Sequence primitive = (ASN1Sequence) stream.readObject();
        Enumeration enumeration = primitive.getObjects();
        BigInteger R = ((ASN1Integer) enumeration.nextElement()).getValue();
        BigInteger S = ((ASN1Integer) enumeration.nextElement()).getValue();
        byte[] bytes = new byte[64];
        byte[] r = format(R.toByteArray());
        byte[] s = format(S.toByteArray());
        System.arraycopy(r, 0, bytes, 0, 32);
        System.arraycopy(s, 0, bytes, 32, 32);
        return bytes;
    }

    /**
     * bc加解密使用旧标c1||c3||c2，此方法在解密前调用，将密文转化为c1||c2||c3再去解密
     *
     * @param c1c3c2
     * @return
     */
    public static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2) {
        final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; // sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
        final int c3Len = 32; // new SM3Digest().getDigestSize();
        byte[] result = new byte[c1c3c2.length];
        System.arraycopy(c1c3c2, 0, result, 0, c1Len); // c1: 0->65
        System.arraycopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.length
                - c1Len - c3Len); // c2
        System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - c3Len, c3Len); // c3
        return result;
    }

    /**
     * bc加解密使用旧标c1||c2||c3，此方法在加密后调用，将结果转化为c1||c3||c2
     *
     * @param c1c2c3
     * @return
     */
    public static byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3) {
        final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; // sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
        final int c3Len = 32; // new SM3Digest().getDigestSize();
        byte[] result = new byte[c1c2c3.length];
        System.arraycopy(c1c2c3, 0, result, 0, c1Len); // c1
        System.arraycopy(c1c2c3, c1c2c3.length - c3Len, result, c1Len, c3Len); // c3
        System.arraycopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.length
                - c1Len - c3Len); // c2
        return result;
    }

    public static byte[] encryptSM2ForDER(byte[] c1c3c2) {
        byte[] result = null;
        try {
            final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; // sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
            final int c3Len = 32; // new SM3Digest().getDigestSize();
            byte[] c1 = new byte[c1Len];
            byte[] c3 = new byte[32];
            byte[] c2 = new byte[c1c3c2.length - c1Len - c3Len];
            System.arraycopy(c1c3c2, 0, c1, 0, c1Len); // c1: 0->65
            System.arraycopy(c1c3c2, c1Len + c3Len, c2, 0, c1c3c2.length
                    - c1Len - c3Len); // c2
            System.arraycopy(c1c3c2, c1Len, c3, 0, c3Len); // c3

            System.out.println("c1:" + CryptoUtil.bin2hex(c1));
            System.out.println("c2:" + CryptoUtil.bin2hex(c2));
            System.out.println("c3:" + CryptoUtil.bin2hex(c3));

            byte[] xValue = new byte[32];
            byte[] yValue = new byte[32];
            System.arraycopy(c1c3c2, 1, xValue, 0, 32); // c1
            System.arraycopy(c1c3c2, c1Len / 2 + 1, yValue, 0, 32); // c1
            System.out.println("x1:" + CryptoUtil.bin2hex(xValue));
            System.out.println("x2:" + CryptoUtil.bin2hex(yValue));

//            DEROctetString x = new DEROctetString(xValue);
//            DEROctetString y = new DEROctetString(yValue);
//            DERInteger x = new DERInteger(xValue);
//            DERInteger y = new DERInteger(yValue);
            DEROctetString derDig = new DEROctetString(c3);
            DEROctetString derEnc = new DEROctetString(c2);
            ASN1EncodableVector v = new ASN1EncodableVector();
            v.add(new ASN1Integer(new BigInteger(1, xValue)));
            v.add(new ASN1Integer(new BigInteger(1, yValue)));
//            v.add(x);
//            v.add(y);
            v.add(derDig);
            v.add(derEnc);
            DERSequence seq = new DERSequence(v);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
//            DEROutputStream dos = new DEROutputStream(bos);
            ASN1OutputStream dos =  ASN1OutputStream.create(bos);
            dos.writeObject(seq);
            result = bos.toByteArray();
            System.out.println("bos:" + CryptoUtil.bin2hex(result));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }

    public static byte[] encrypt(byte[] publicKey, byte[] data) throws IOException {

        if (publicKey == null || publicKey.length == 0) {
            return null;
        }

        if (data == null || data.length == 0) {
            return null;
        }

        byte[] source = new byte[data.length];
        System.arraycopy(data, 0, source, 0, data.length);

        Cipher cipher = new Cipher();
        SM2 sm2 = SM2.Instance();
        ECPoint userKey = sm2.ecc_curve.decodePoint(publicKey);

        ECPoint c1 = cipher.initEnc(sm2, userKey);
        cipher.encrypt(source);
        byte[] c3 = new byte[32];
        cipher.dofinal(c3);

        ASN1Integer x = new ASN1Integer(c1.normalize().getXCoord().toBigInteger());
        ASN1Integer y = new ASN1Integer(c1.normalize().getYCoord().toBigInteger());
        DEROctetString derDig = new DEROctetString(c3);
        DEROctetString derEnc = new DEROctetString(source);
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(x);
        v.add(y);
        v.add(derDig);
        v.add(derEnc);
        DERSequence seq = new DERSequence(v);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
//        DEROutputStream dos = new DEROutputStream(bos);
        ASN1OutputStream dos =  ASN1OutputStream.create(bos);
        dos.writeObject(seq);
        return bos.toByteArray();
    }

    public static byte[] decrypt(byte[] privateKey, byte[] encryptedData) throws IOException {
        if (privateKey != null && privateKey.length != 0) {
            if (encryptedData != null && encryptedData.length != 0) {
                byte[] enc = new byte[encryptedData.length];
                System.arraycopy(encryptedData, 0, enc, 0, encryptedData.length);
                SM2 sm2 = SM2.Instance();
                BigInteger userD = new BigInteger(1, privateKey);
                ByteArrayInputStream bis = new ByteArrayInputStream(enc);
                ASN1InputStream dis = new ASN1InputStream(bis);
                ASN1Primitive derObj = dis.readObject();
                ASN1Sequence asn1 = (ASN1Sequence) derObj;
                ASN1Integer x = (ASN1Integer) asn1.getObjectAt(0);
                ASN1Integer y = (ASN1Integer) asn1.getObjectAt(1);
                ECPoint c1 = sm2.ecc_curve.createPoint(x.getValue(), y.getValue());
                Cipher cipher = new Cipher();
                cipher.initDec(userD, c1);
                DEROctetString data = (DEROctetString)asn1.getObjectAt(3);
                enc = data.getOctets();
                cipher.decrypt(enc);
                byte[] c3 = new byte[32];
                cipher.dofinal(c3);
                return enc;
            } else {
                return null;
            }
        } else {
            return null;
        }
    }
}
